package com.hy.dp.bagProblem.fullBag;

public class ChangeExchange02 {

    /**
     *  322. 零钱兑换
     * 力扣题目链接
     *
     * 给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额，返回 -1。
     *
     * 你可以认为每种硬币的数量是无限的。
     *
     * 示例 1： 输入：coins = [1, 2, 5], amount = 11 输出：3 解释：11 = 5 + 5 + 1
     *
     * 思路
     * 在动态规划：518.零钱兑换II中我们已经兑换一次零钱了，这次又要兑换，套路不一样！
     *
     * 题目中说每种硬币的数量是无限的，可以看出是典型的完全背包问题。
     *
     * 动规五部曲分析如下：
     * 1.确定dp数组以及下标的含义
     * dp[j]：凑足总额为j所需钱币的最少个数为dp[j]
     *
     * 2.确定递推公式
     * 得到dp[j]（考虑coins[i]），只有一个来源，dp[j - coins[i]]（没有考虑coins[i]）。
     * 凑足总额为j - coins[i]的最少个数为dp[j - coins[i]]，那么只需要加上一个钱币coins[i]即dp[j - coins[i]] + 1就是dp[j]（考虑coins[i]）
     * 所以dp[j] 要取所有 dp[j - coins[i]] + 1 中最小的。
     * 递推公式：dp[j] = min(dp[j - coins[i]] + 1, dp[j]);
     *
     * 3.dp数组如何初始化
     * 首先凑足总金额为0所需钱币的个数一定是0，那么dp[0] = 0;
     * 其他下标对应的数值呢？
     * 考虑到递推公式的特性，dp[j]必须初始化为一个最大的数，否则就会在min(dp[j - coins[i]] + 1, dp[j])比较的过程中被初始值覆盖。
     * 所以下标非0的元素都是应该是最大值。
     *
     * 4.确定遍历顺序
     * 本题求钱币最小个数，那么钱币有顺序和没有顺序都可以，都不影响钱币的最小个数。
     * 所以本题并不强调集合是组合还是排列。
     * 如果求组合数就是外层for循环遍历物品，内层for遍历背包。
     * 如果求排列数就是外层for遍历背包，内层for循环遍历物品。
     * 所以本题的两个for循环的关系是：外层for循环遍历物品，内层for遍历背包或者外层for遍历背包，内层for循环遍历物品都是可以的！
     * 那么我采用coins放在外循环，target在内循环的方式。
     *
     * 本题钱币数量可以无限使用，那么是完全背包。所以遍历的内循环是正序
     *
     * 综上所述，遍历顺序为：coins（物品）放在外循环，target（背包）在内循环。且内循环正序。
     *
     * 5.举例推导dp数组
     * 以输入：coins = [1, 2, 5], amount = 5为例
     *
     *
     * @return
     */
    public static int changeExchange02(int [] coins,int amount){
        // 1.定义dp数组以及下标定义
        int [] dp = new int[amount + 1];
        // 2. 推导 递推式
        int max = Integer.MAX_VALUE;
        // 3.初始化
        for (int i = 0; i < dp.length; i++) {
            dp[i] = max;
        }
        //当金额为0时需要的硬币数目为0
        dp[0] = 0;
        //4. 循环遍历
        for (int i = 0; i < coins.length; i++) {
            //正序遍历：完全背包每个硬币可以选择多次
            for (int j = coins[i]; j <= amount; j++) {
                //只有dp[j-coins[i]]不是初始最大值时，该位才有选择的必要
                if (dp[j - coins[i]] != max){
                    //选择硬币数目最小的情况
                    dp[j] = Math.min(dp[j],dp[j- coins[i]] + 1);
                }
            }
        }
        return dp[amount] == max ? -1 : dp[amount];
    }

    public static void main(String[] args) {
        int [] coins = {1,2,5};
        int amount = 11;
        System.out.println("res: "+changeExchange02(coins,amount));
    }
}
